Event.observe(window, "load", function() {

    /**
     * Envoi du formulaire lorsqu'une case à cocher est cliquée.
     */
    $$("input.caseGrp, input.caseEns").each(function(checkbox) {
        checkbox.onclick = function() {
            $("chpRecherche").value = "";
            $(this).up("form").submit();
        };
    });

    $("btRech").onclick = function() {
        $(this).up("form").submit();
    }

    /**
     * Définit le comportement du "fieldset" (qui contient les boutons
     * et les messages du solveur) lorsqu'on clique sur la légende.
     */
    var fieldset = $("fsSolveur");
    fieldset.down("legend").onclick = function() {
        var conteneur = fieldset.down("div");
        if(fieldset.hasClassName("estPlie"))
            new Effect.BlindDown(conteneur, {"duration": 0.2});
        else
            new Effect.BlindUp(conteneur, {"duration": 0.2});
        fieldset.toggleClassName("estPlie");
    }

    /**
     * Définit l'action à effectuer lorsqu'un des boutons
     * relatifs au solveur est cliqué.
     */
    $("btVerifier").onclick = requeteVerification;
    $("btResoudre").onclick = requeteResolution;

    // Chaque prestation contenue dans une case est coloriée :
    colorierPrestationCasee("table.edt div.prestation");

    // Chaque prestation placée sur l'EDT peut être déplacée.
    definirDeplacPrestCasees("table.edt div.prestation");

    // Les prestations doivent pouvoir être déposées sur n'importe quelle case.
    definirDropAreasCases("table.edt div.case");

    // Chaque prestation qui n'est pas encore sur l'EDT peut être aussi déplacée.
    definirDeplacPrestNonCasees("#prestNonCasees div.prestation");

    // Le conteneur des prestations non placées doit pouvoir recevoir des prestations.
    definirDropAreasConteneurPrestNC();

    // Les prestations non casées peuvent être triées entre elles.
    if($("prestNonCasees"))
        activerTriPrestNonCasees();
});

/**
 * Exécute une requête AJAX qui va lancer une vérification sur le serveur,
 * et retourner la liste des éventuels conflits trouvés.
 */
function requeteVerification() {
    new Ajax.Updater (
        "msgResolution",
        "UtiliserSolveur.do?action=verifierCoherenceEDT",
        {
            asynchronous: true,
            onCreate: function() {
                bloquerDepots();
                $("animationAttente").show();
            },
            onComplete: function() {
                debloquerDepots();
                new Effect.BlindUp("animationAttente");
                new Effect.Shake("msgResolution");
            }
        }
    );
}

/**
 * Exécute une requête AJAX qui va lancer la résolution du problème
 * de l'emploi du temps sur le serveur. L'affichage de l'EDT sera
 * actualisé si la résolution a réussi.
 */
function requeteResolution() {
    new Ajax.Updater (
        "msgResolution",
        "UtiliserSolveur.do?action=resoudreEDT",
        {
            asynchronous: true,
            onCreate: function() {
                bloquerDepots();
                $("animationAttente").show();
            },
            onComplete: function() {
                if($("estResolu")) {
                    window.location = "GererPlanning.do?action=afficher&resolu=1";
                }
                else {
                    debloquerDepots();
                    new Effect.BlindUp("animationAttente");
                    new Effect.Shake("msgResolution");
                }
            }
        }
    );
}

/**
 * Empêche le dépôt de prestation sur les cases.
 */
function bloquerDepots() {
    $$("table.edt div.case, #prestNonCasees").each(function(c) {
        Droppables.remove(c);
    });
}

/**
 * Réactive la possibilité de déposer des prestations.
 */
function debloquerDepots() {
    definirDropAreasCases("table.edt div.case");
    definirDropAreasConteneurPrestNC();
}

/**
 * Active le tri des prestations non casées dans leur conteneur.
 */
function activerTriPrestNonCasees() {
    Sortable.create("prestNonCasees", {tag: "div",
                                       only: "nonCasee",
                                       handle:/*poignée:*/ "couleur"});
}

/**
 * Supprime la gestion du tri des prestations non casées.
 */
function enleverTriPrestNonCasees() {
    Sortable.destroy("prestNonCasees");
}

function colorierPrestationCasee(selectors) {
    $$(selectors).each(function(prestation) {
        var caSe = $(prestation).up("td.case");
        prestation.style.backgroundColor = caSe.style.backgroundColor;
        prestation.style.color = caSe.style.color;
    });
}

/**
 * Rend "draggables" tous les blocs (correspondant aux prestations casées)
 * sélectionnés par la règle CSS passée en paramètre.
 * @param selectors : sélecteurs CSS (ou directement un tableau de prestations,
 *                     ou une seule prestation sous forme de HTMLElement)
 */
function definirDeplacPrestCasees(selectors) {

    var prestations = [];
    if(typeof(selectors) == "object") // <~> (selectors instanceof HTMLElement)
        prestations = [selectors];
    else if(selectors.push) // <~> is_array(selectors)
        prestations = selectors;
    else //if(typeof(selectors) == "string")
        prestations = $$(selectors);
    
    prestations.each(function(prestation) {
        var TD = null;
        var bgColorTD = "";
        var fontColorTD = "";
        new Draggable(
            prestation,
            {
                revert: true,
                scroll: window,
                starteffect: function(elt) {
                    TD = elt.up("td.case");
                    bgColorTD = TD.style.backgroundColor;
                    elt.style.backgroundColor = bgColorTD;
                    TD.style.backgroundColor = "white";
                    fontColorTD = TD.style.color;
                    elt.style.color = fontColorTD;
                    TD.style.color = "inherit";
                },
                endeffect: function(elt) {
                    if(elt.up("td.case") == TD) { // si la prestation revient dans sa case
                        TD.style.backgroundColor = bgColorTD;
                        TD.style.color = fontColorTD;
                    }
                }
            }
        );
    });
}

/**
 * Rend "draggables" tous les blocs (correspondant aux prestations non casées)
 * sélectionnés par la règle CSS passée en paramètre.
 * @param selectors : sélecteurs CSS (ou directement un tableau de prestations,
 *                     ou une seule prestation sous forme de HTMLElement)
 */
function definirDeplacPrestNonCasees(selectors) {

    var prestations = [];
    if(typeof(selectors) == "object") // <~> (selectors instanceof HTMLElement)
        prestations = [selectors];
    else if(selectors.push) // <~> is_array(selectors)
        prestations = selectors;
    else //if(typeof(selectors) == "string")
        prestations = $$(selectors);

    prestations.each(function(prestation) {
        new Draggable(
            prestation,
            {
                revert: true,
                scroll: window
            }
        );
    });
}

/**
 * Rend "droppables" toutes les cases de tableau (censées accueillir une prestation
 * éventuellement) sélectionnés par la règle CSS passée en paramètre.
 * @param selectors : sélecteurs CSS
 */
function definirDropAreasCases(selectors) {
    
    $$(selectors).each(function(c) {
        var classesAcceptees = []; // tableau qui contiendra les classes des éléments "draggables"
                                   // qui seront acceptés lorsqu'ils seront déposés
        $w($(c).className).each(function(className) {
            if(/^(groupe\d+|enseignant\d+)$/i.test(className))
                classesAcceptees.push(className);
        });
        Droppables.add(c, {
            accept: classesAcceptees,
            hoverclass: "hoverActive",
            onDrop: function(prestation, caseEDT) {

                if(prestation.hasClassName("nonCasee")) {
                    deplacerPrestNonCasee(prestation, caseEDT);
                }
                else { // déplacement d'une prestation déjà casée sur une autre case
                    deplacerPrestDejaCasee(prestation, caseEDT);
                }
            }
        });
    });
}

/**
 * Rend "droppable" le conteneur div qui comporte l'ensemble des prestations
 * non casées. Toutes les prestations placées dans l'EDT pourront y être
 * déposées pour les supprimer de l'EDT.
 */
function definirDropAreasConteneurPrestNC() {

    if($("prestNonCasees")) {
        Droppables.add("prestNonCasees", {
            accept: "casee", // seul les prestations casées peut être déposées
            hoverclass: "hoverActive",
            onDrop: function(prestation, conteneur) {
                if(prestation.hasClassName("nonCasee"))
                    return; // on ne fait rien qd une prestation non casée
                            // est relâchée sur le conteneur
                deplacerPrestDansConteneur(prestation, conteneur);
            }
        });
    }
}

function nettoyerCase(caseEDT) {
    caseEDT.descendants().each(function(child) {
       child.remove();
    });
}

/**
 * Déplacement d'une prestation casée dans un autre créneau.
 */
function deplacerPrestDejaCasee(prestation, nvCase) {

    if(prestation.up("div.case") == nvCase)
        return; // on ne fait rien qd la prestation est lâchée sur sa propre case

    var numCreneau = nvCase.down(".numCreneau").innerHTML.strip();
    var idPrestAPlacer = prestation.down(".idPrest").innerHTML.strip();
    var idPrestARemplacer = null; // sera utilisée si la case est déjà occupée
    var prestARemplacer = null; // sera utilisée si la case est déjà occupée

    // définition d'une fonction callback pour déplacer la prestation
    var deplacer = function() {
        var ancienneCase = prestation.parentNode;
        ancienneCase.removeClassName("occupee");
        ancienneCase.up("td.case").style.backgroundColor = "white";
        ancienneCase.up("td.case").style.color = "inherit";
        ancienneCase.removeChild(prestation);
        nvCase.appendChild(prestation);
        var bg = prestation.style.backgroundColor;
        nvCase.up("td.case").style.backgroundColor = bg;
        var fontColor = prestation.style.color;
        nvCase.up("td.case").style.color = fontColor;
        nvCase.addClassName("occupee");
    };

    // définition d'une fonction callback pour déplacer la prestation
    // remplacée dans la liste des prestations non casées
    var callbackRemplacement = function() {
        var resultat = $("resultat").innerHTML.strip().toLowerCase();
        if(resultat == "succes") {
            // la prestation remplacée est alors mise dans la pile des prestation non casées
            var prestRemplNonCasee = convertEnPrestNonCasee(prestARemplacer);
            definirDeplacPrestNonCasees(prestRemplNonCasee);
            $("hautPilePrest").insert({"after": prestRemplNonCasee});
            actualiserListePrestNC();
        }
    };

    if(nvCase.hasClassName("occupee")) {
        var msg = "Une prestation existe déjà dans cette case, " +
                  "voulez-vous la remplacer ?";
        if(confirm(msg)) {
            idPrestARemplacer = nvCase.down(".idPrest").innerHTML.strip();
            prestARemplacer = nvCase.down(".prestation").cloneNode(true);
            prestARemplacer.style.backgroundColor = nvCase.up("td.case").style.backgroundColor;
            prestARemplacer.style.color = nvCase.up("td.case").style.color;
            
            nettoyerCase(nvCase); // enlève d'abord la prestation à remplacer
            deplacer(); // déplace la prestation dans le DOM
            requeteRemplacerPrest(idPrestAPlacer, idPrestARemplacer, callbackRemplacement);
        }
    }
    else { // si la case est libre
        deplacer(); // déplace la prestation dans le DOM
        requetePlacerPrest(idPrestAPlacer, numCreneau);
    }
}

/**
 * Déplacement d'une prestation non casée dans un créneau de l'EDT.
 */
function deplacerPrestNonCasee(prestNonCasee, nvCase) {

    var numCreneau = nvCase.down(".numCreneau").innerHTML.strip();
    var idPrestAPlacer = prestNonCasee.down(".idPrest").innerHTML.strip();
    var remplacement = false;
    var idPrestARemplacer = null; // sera utilisée si la case est déjà occupée
    var prestARemplacer = null; // sera utilisée si la case est déjà occupée

    // définition d'une fonction callback pour déplacer la prestation
    var deplacer = function() {
        var ancienConteneur = prestNonCasee.parentNode;
        ancienConteneur.removeChild(prestNonCasee);
        actualiserListePrestNC();

        var prestation = convertEnPrestCasee(prestNonCasee);
        nvCase.appendChild(prestation);
        var bg = prestation.style.backgroundColor;
        nvCase.up("td.case").style.backgroundColor = bg;
        var fontColor = prestation.style.color;
        nvCase.up("td.case").style.color = fontColor;
        nvCase.addClassName("occupee");
        // définit le comportement de l'élément HTML (qui vient d'être créé) de la prestation
        definirDeplacPrestCasees(prestation);
    };

    // définition d'une fonction callback pour défaire le déplacement en cas
    // d'erreurs et pour, en cas de remplacement, déplacer la prestation
    // remplacée dans la liste des prestations non casées
    var callbackDeplacement = function() {
        var resultat = $("resultat").innerHTML.strip().toLowerCase();
        if(resultat == "erreur") {
            // le déplacement effectué est alors défait :
            // la prestation est replacée en haut de la pile des prestations non casées
            $("hautPilePrest").insert({"after": prestNonCasee});
            actualiserListePrestNC();
        }
        else if(resultat == "succes") {
            if(remplacement) { // s'il s'agit d'un remplacement
                // la prestation remplacée est alors mise dans la pile des prestation non casées
                var prestRemplNonCasee = convertEnPrestNonCasee(prestARemplacer);
                definirDeplacPrestNonCasees(prestRemplNonCasee);
                $("hautPilePrest").insert({"after": prestRemplNonCasee});
                actualiserListePrestNC();
            }
        }
    };

    if(nvCase.hasClassName("occupee")) {
        remplacement = true;
        var msg = "Une prestation existe déjà dans cette case, " +
                  "voulez-vous la remplacer ?";
        if(confirm(msg)) {
            idPrestARemplacer = nvCase.down(".idPrest").innerHTML.strip();
            prestARemplacer = nvCase.down(".prestation").cloneNode(true);
            prestARemplacer.style.backgroundColor = nvCase.up("td.case").style.backgroundColor;
            prestARemplacer.style.color = nvCase.up("td.case").style.color;

            nettoyerCase(nvCase); // enlève d'abord la prestation à remplacer
            deplacer(); // déplace la prestation dans le DOM
            requeteRemplacerPrest(idPrestAPlacer, idPrestARemplacer, callbackDeplacement);
        }
    }
    else { // si la case est libre
        remplacement = false;
        deplacer(); // déplace la prestation dans le DOM
        requetePlacerPrest(idPrestAPlacer, numCreneau, callbackDeplacement);
    }
}

/**
 * Déplacement d'une prestation casée dans le conteneur des
 * prestations non casées (enlève la prestation de l'EDT).
 */
function deplacerPrestDansConteneur(prestationCasee, conteneur) {

    var ancienneCase = prestationCasee.parentNode;
    ancienneCase.removeClassName("occupee");
    ancienneCase.up("td.case").style.backgroundColor = "white";
    ancienneCase.up("td.case").style.color = "inherit";
    ancienneCase.removeChild(prestationCasee);

    var prestation = convertEnPrestNonCasee(prestationCasee);
    conteneur.appendChild(prestation);
    actualiserListePrestNC();
    // définit le comportement de l'élément HTML (qui vient d'être créé) de la prestation
    definirDeplacPrestNonCasees(prestation);

    var idPrestation = prestation.down(".idPrest").innerHTML.strip()
    requeteEnleverPrest(idPrestation);
}

/**
 * Transforme une prestation (conteneur div) non casée en un conteneur
 * div qui sera placé dans le tableau de l'EDT.
 * @param prestNonCasee
 * @return Conteneur 'div' prestation casée
 */
function convertEnPrestCasee(prestNonCasee) {
    var couleur = prestNonCasee.down(".couleur");
    var idPrest = prestNonCasee.down(".idPrest");
    var moduleUE = prestNonCasee.down(".moduleUE");
    var module = prestNonCasee.down(".module");
    var enseignant = prestNonCasee.down(".enseignant");

    var classDivPrest = "prestation casee ";
    var regexp = /^\s*(groupe\d+|enseignant\d+)\s*$/i; // ~ groupeX ou enseignantX ou X est un nombre
    classDivPrest += $w(prestNonCasee.className).grep(regexp).join(" ");

    var prestCasee =
        Builder.node("div", {className: classDivPrest,
                             style: "background-color:"+couleur.style.backgroundColor+"; "+
                                    "color:"+couleur.style.color+";"}, [
           Builder.node("span", {className: "idPrest cache"}, idPrest.innerHTML),
           Builder.node("div", {className: "module",
                                title: moduleUE.title}, module.innerHTML),
           Builder.node("div", {className: "enseignant"}, enseignant.innerHTML)
        ]);
    return prestCasee;
}

/**
 * Transforme une prestation (conteneur div) casée en un conteneur
 * div qui sera placé dans la liste des prestations sans créneau.
 * @param prestCasee
 * @return Conteneur 'div' prestation non casée
 */
function convertEnPrestNonCasee(prestCasee) {
    var idPrest = prestCasee.down(".idPrest");
    var module = prestCasee.down(".module");
    var enseignant = prestCasee.down(".enseignant");

    var classDivPrest = "prestation nonCasee ";
    var regexp = /^\s*(groupe\d+|enseignant\d+)\s*$/i; // ~ groupeX ou enseignantX ou X est un nombre
    classDivPrest += $w(prestCasee.className).grep(regexp).join(" ");
    
    var prestNonCasee =
        Builder.node("div", {className: classDivPrest}, [
           Builder.node("div", {style: "background-color:"+prestCasee.style.backgroundColor+"; "+
                                       "color:"+prestCasee.style.color+";",
                                className: "couleur"}, ""),
           Builder.node("span", {}, [
               "Id prestation : ",
               Builder.node("strong", {className: "idPrest"}, idPrest.innerHTML)
           ]),
           " - ",
           Builder.node("span", {className: "moduleUE",
                                 title: module.title}, [
               "Module : ",
               Builder.node("strong", {className: "module"}, module.innerHTML)
           ]),
           " - ",
           Builder.node("span", {}, [
               "Enseignant :",
               Builder.node("strong", {className: "enseignant"}, enseignant.innerHTML)
           ])
        ]);
    return prestNonCasee;
}

/**
 * Actualise la liste des prestations non casées, c'est-à-dire met à jour
 * le message qui indique si toutes les prestations ont été casées ou non.
 * Et aussi, redéfinit le tri des prestations dans le conteneur en
 * particulier si une nouvelle prestation a été ajoutée.
 */
function actualiserListePrestNC() {

    var nbPrestNC = $$("#prestNonCasees .prestation").length;
    if(nbPrestNC > 0)
        $("titreMsgPrestTtesCasees").addClassName("cache");
    else
        $("titreMsgPrestTtesCasees").removeClassName("cache");

    enleverTriPrestNonCasees();
    activerTriPrestNonCasees();
}

function requetePlacerPrest(idPrestation, numCreneau, callbackOnCompl) {
    new Ajax.Updater (
        "edt",
        "GererPlanning.do?action=placerPrestation",
        {
            method: "post",
            asynchronous: true,
            parameters: {"idPrestation": idPrestation, "numCreneau": numCreneau},
            onSuccess: onSuccessMajEdt,
            onComplete: function() {
                onCompleteMajEdt();
                if(callbackOnCompl)
                    callbackOnCompl();
            }
        }
    );
}

function requeteRemplacerPrest(idPrestAPlacer, idPrestARemplacer, callbackOnCompl) {
    new Ajax.Updater (
        "edt",
        "GererPlanning.do?action=remplacerPrestation",
        {
            method: "post",
            asynchronous: true,
            parameters: {"idPrestation": idPrestAPlacer, "idPrestARemplacer": idPrestARemplacer},
            onSuccess: onSuccessMajEdt,
            onComplete: function() {
                onCompleteMajEdt();
                if(callbackOnCompl)
                    callbackOnCompl();
            }
        }
    );
}

function requeteEnleverPrest(idPrestation, callbackOnCompl) {
    new Ajax.Updater (
        "edt",
        "GererPlanning.do?action=enleverPrestation",
        {
            method: "post",
            asynchronous: true,
            parameters: {"idPrestation": idPrestation},
            onSuccess: onSuccessMajEdt,
            onComplete: function() {
                onCompleteMajEdt();
                if(callbackOnCompl)
                    callbackOnCompl();
            }
        }
    );
}

/**
 * Callback à utiliser après mise à jour de l'EDT pour afficher les
 * messages cachés contenus dans la mise à jour et pour réabonner
 * les cases et les prestations aux bons événements.
 */
function onCompleteMajEdt() {
    // récupération des messages contenus dans la mise
    // à jour, et affichage dans le bloc prévu pour
    var messages = $$("#edt .messagesCaches > div")[0];
    if(messages != undefined) {
        messages.onclick = function() {
            new Effect.BlindUp(this); // enlève les messages (avec un effet) sur clic
        }
        $("messages").update(messages); // affiche les messages dans la page
        new Effect.Shake('messages'); // secoue le conteneur de messages
    }

    // on doit aussi redéfinir le comportement de chaque
    // élément du tableau mis à jour
    definirDeplacPrestCasees("table.edt div.prestation");
    colorierPrestationCasee("table.edt div.prestation");
    definirDropAreasCases("table.edt div.case");
}

/**
 * Callback à utiliser après la réception de la mise à jour de l'EDT et juste
 * avant sa mise en place, c'est-à-dire sur l'événement "success" d'Ajax.Updater.
 * Il supprime le comportement "droppable" des anciennes cases (celles qui
 * sont sur le point d'être remplacées).
 */
function onSuccessMajEdt() {
    $$("table.edt div.case").each(function(c) {
        Droppables.remove(c);
    });
}
